package com.wh.wisdomsite.xbox.ba.service.impl;


import com.jiangshikun.parrot.common.utils.FastJsonUtils;
import com.jiangshikun.parrot.dao.RedisBaseDao;
import com.jiangshikun.parrot.dao.StringDao;
import com.wh.wisdomsite.xbox.ba.entity.CommonFile;
import com.wh.wisdomsite.xbox.ba.entity.FileInUtil;
import com.wh.wisdomsite.xbox.ba.entity.UploadFile;
import com.wh.wisdomsite.xbox.ba.service.ICommonFileService;
import com.wh.wisdomsite.xbox.ba.service.IFileService;
import com.wh.wisdomsite.xbox.common.Constants;
import com.wh.wisdomsite.xbox.common.annotation.ModuleTypeEnum;
import com.wh.wisdomsite.xbox.common.reponse.CommonResult;
import com.wh.wisdomsite.xbox.core.session.impl.SimpleSession;
import com.wh.wisdomsite.xbox.pojo.ba.MinioUpFileIn;
import com.wh.wisdomsite.xbox.pojo.ba.MinioUpFileOut;
import com.wh.wisdomsite.xbox.utils.Const;
import com.wh.wisdomsite.xbox.utils.encrypt.md5.Md5Tool_One;
import io.minio.MinioClient;
import io.minio.errors.*;
import jdk.nashorn.internal.runtime.regexp.joni.exception.InternalException;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 公共附件上传
 * @author 谢泽鹏
 * @version V1.0
 * @Package com.wh.wisdomsite.xbox.ba.service.impl
 * @date 2014年10月8日 下午4:02:14
 * @Copyright 个人版权所有
 * @Company 贵州万华科技有限公司Copyright (c) 2018
 */
@Service("fileService")
@Transactional(rollbackFor = {Exception.class,RuntimeException.class})
public class IFileServiceImpl implements IFileService {

    private static final Logger logger = LoggerFactory.getLogger(IFileServiceImpl.class);

    private static final FastDateFormat DATE_FORMAT = FastDateFormat.getInstance("yyyy/MM/dd");

    private static final FastDateFormat DATE_FORMAT_ = FastDateFormat.getInstance("yyyyMMdd");

    private StringDao stringDao = RedisBaseDao.getStringDao();

    @Autowired
    private ICommonFileService iCommonFileService;


    /**
     * 重试次数
     */
    private static int RETRY_NUM = 3;

    /**
     * 上传文件失效时间（秒）
     */
    private static  Integer UPLOAD_EXPIRESTIME = 3 * 24 * 3600;

    /**
     * 使用流上传附件
     * @param fileInUtil  文件信息
     * @param minioUpFileIn 上传入参
     */
    @Override
    public CommonResult uploadFile(FileInUtil fileInUtil, MinioUpFileIn minioUpFileIn) {
        List<FileInUtil> fileInUtils = new ArrayList<>();
        fileInUtils.add(fileInUtil);
        return uploadFile(fileInUtils, minioUpFileIn);
    }

    /**
     * 批量使用流上传附件
     * @param fileInUtils   文件信息
     * @param minioUpFileIn 上传文件入参
     */
    @Override
    public CommonResult uploadFile(List<FileInUtil> fileInUtils, MinioUpFileIn minioUpFileIn) {
        logger.info("uploadFile附件上传->>开始");
        logger.info("uploadFile附件上传fileInUtils = {}", JSONArray.fromObject(fileInUtils));
        logger.info("uploadFile附件上传OSSUpFileIn = {}", JSONObject.fromObject(minioUpFileIn));
        CommonResult result = new CommonResult();
        try {
            result.setCode(Constants.CODE_SUCCESS);
            result.setMessage("附件上传成功！");
            long start = System.currentTimeMillis();
            logger.info("uploadFile附件上传开始..." + start);
            if (fileInUtils != null && fileInUtils.size() > 0 && StringUtils.isNotBlank(minioUpFileIn.getBusinessId()) && StringUtils.isNotBlank(minioUpFileIn.getModelName()) && StringUtils.isNotBlank(minioUpFileIn.getModelCode()) && StringUtils.isNotBlank(minioUpFileIn.getFunName()) && StringUtils.isNotBlank(minioUpFileIn.getFunCode()) && minioUpFileIn.getFkType() != null) {
                MinioClient minioClient = null;
                List<MinioUpFileOut> minioUpFileOuts = new ArrayList<>();
                MinioUpFileOut minioUpFileOut;
                minioClient = getMinioClient();
                byte[] bytes;
                for (FileInUtil file : fileInUtils) {
                    if (file != null) {
                        bytes = file.getData();
                        //附件原名称
                        String fileName = file.getFileName();
                        //附件原名称
                        if (StringUtils.isNotBlank(fileName) && null != bytes) {
                            //公共附件上传方法
                            minioUpFileOut = upload(minioUpFileIn, bytes, fileName, minioClient);
                            minioUpFileOuts.add(minioUpFileOut);
                        }
                    }
                }
                result.setResult(minioUpFileOuts.size() > 1 ? minioUpFileOuts : minioUpFileOuts.get(0));
                if (null != minioClient) {
                    //minioClient.
                }
            } else {
                logger.info("uploadFile附件上传->>参数格式错误");
                result.setCode(Constants.CODE_FAIL);
                result.setMessage("上传文件格式错误！");
            }
            logger.info("uploadFile附件上传成功, 上传耗时：{}毫秒", System.currentTimeMillis() - start);
        }catch (Exception e){
            e.printStackTrace();
            logger.error("uploadFile附件上传错误, 出现异常msg = {}", e.getMessage());
            result.setCode(Constants.CODE_FAIL);
            result.setMessage("附件上传失败！");
        }
            logger.info("uploadFile附件上传->>结束");
            return result;
    }




    /**
     * 公共附件上传方法
     * @param minioUpFileIn  上传文件入参
     * @param bytes          上传文件byte流
     * @param sourceFileName 源文件名称
     * @param minioClient
     * @return
     */
    private MinioUpFileOut upload(MinioUpFileIn minioUpFileIn, byte[] bytes, String sourceFileName, MinioClient minioClient) throws InvalidBucketNameException, NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, NoResponseException, XmlPullParserException, ErrorResponseException, InternalException, InvalidArgumentException, InsufficientDataException, io.minio.errors.InternalException, InvalidExpiresRangeException {
        //输出对象
        MinioUpFileOut minioUpFileOut = new MinioUpFileOut();
        //生成附件uuid
        String uuid = getUUID();
        //生成附件名称
        String fileName = DATE_FORMAT_.format(new Date()) + getRandomName(uuid) + sourceFileName.substring(sourceFileName.lastIndexOf("."));
        //附件模块类型枚举值
        String type = null;
        try {
            type = minioUpFileIn.getFkType().getType();
        } catch (Exception e) {
            type = ModuleTypeEnum.OTHER.getType();
            logger.warn("枚举类型出错，手动设置为默认值.");
        }
        //上传路径
        String path = type + "/" + DATE_FORMAT.format(new Date()) + "/" + fileName;
        logger.info("上传附件名称：{}", fileName);
        logger.info("上传附件路径：{}", path);
        //执行上传
        putObjectWithRetry(Const.MINIO_BUCKETNAME, path, new ByteArrayInputStream(bytes), MediaType.APPLICATION_OCTET_STREAM_VALUE, minioClient);

        //获取上传路径
        String minioPath = minioClient.presignedGetObject(Const.MINIO_BUCKETNAME,  path, UPLOAD_EXPIRESTIME);
        //存放redis容器中
        UploadFile uploadFile = new UploadFile();
        uploadFile.setFileId(uuid);
        uploadFile.setObjectName(path);
        uploadFile.setExpiresTime(UPLOAD_EXPIRESTIME);
        uploadFile.setUploadTime(Integer.parseInt(String.valueOf(System.currentTimeMillis() / 1000)));
        uploadFile.setUrl(minioPath);
        stringDao.save(uuid, FastJsonUtils.obj2json(uploadFile), 86400000 * 4);

        logger.info("附件上传结果：{}",  minioPath);
        minioUpFileOut.setSize(bytes.length + "");
        minioUpFileOut.setFix(fileName.substring(fileName.indexOf(".") + 1, fileName.length()));
        minioUpFileOut.setFileId(uuid);
        minioUpFileOut.setUrl(minioPath);
        minioUpFileOut.setMd5(Md5Tool_One.MD5(minioPath));
        return minioUpFileOut;
    }

    @Override
    public Boolean validationUrlOrUpdate(String fileId)  {
        boolean falg = false;
        try {
            logger.info("附件预览->>验证URL->>开始");
            //从redis中获取
            String uploadFileStr = stringDao.getValue(fileId);
            if(StringUtils.isNotEmpty(uploadFileStr)){
                UploadFile uploadFile = FastJsonUtils.json2obj(uploadFileStr, UploadFile.class);
                //判断上传时文件是否过期([系统时间]thisSeconds  - [上传时间]uploadSeconds > [失效时间]expiresTime)
                Integer thisSeconds = Integer.parseInt(String.valueOf(System.currentTimeMillis() / 1000));
                Integer uploadSeconds = uploadFile.getUploadTime();
                Integer expiresTime = uploadFile.getExpiresTime();
                String path = uploadFile.getObjectName();
                if(thisSeconds - uploadSeconds > expiresTime){
                    //大于，已过期
                    MinioClient minioClient = this.getMinioClient();
                    //重新获取Url
                    String minioPath = minioClient.presignedGetObject(Const.MINIO_BUCKETNAME, path, UPLOAD_EXPIRESTIME);

                    //1. 重新存放redis容器中
                    UploadFile newUploadFile = new UploadFile();
                    newUploadFile.setFileId(fileId);
                    newUploadFile.setObjectName(path);
                    newUploadFile.setExpiresTime(UPLOAD_EXPIRESTIME);
                    newUploadFile.setUploadTime(Integer.parseInt(String.valueOf(System.currentTimeMillis() / 1000)));
                    newUploadFile.setUrl(minioPath);
                    stringDao.save(fileId, FastJsonUtils.obj2json(newUploadFile), 86400000 * 4);

                    //2. 执行数据更新
                    //根据fileId更新数据表
                    CommonFile commonFile = iCommonFileService.selectById(fileId);
                    //minioPath更新
                    commonFile.setUrl(minioPath);
                    iCommonFileService.updateById(commonFile);
                }
            }else{
                //reids中已查不出该数据，需要重新从数据库中获取数据进行处理
                //先从库中查询数据
                CommonFile commonFile = iCommonFileService.selectById(fileId);
                //获取path
                String path = commonFile.getUrl().split("\\?")[0].split(Const.MINIO_BUCKETNAME+"/")[1];
                MinioClient minioClient = this.getMinioClient();
                //重新获取Url
                String minioPath = minioClient.presignedGetObject(Const.MINIO_BUCKETNAME, path, UPLOAD_EXPIRESTIME);
                //重新存放redis容器中
                UploadFile newUploadFile = new UploadFile();
                newUploadFile.setFileId(fileId);
                newUploadFile.setObjectName(path);
                newUploadFile.setExpiresTime(UPLOAD_EXPIRESTIME);
                newUploadFile.setUploadTime(Integer.parseInt(String.valueOf(System.currentTimeMillis() / 1000)));
                newUploadFile.setUrl(minioPath);
                stringDao.save(fileId, FastJsonUtils.obj2json(newUploadFile), 86400000 * 4);
                //minioPath更新
                commonFile.setUrl(minioPath);
                iCommonFileService.updateById(commonFile);

            }
            logger.info("附件预览->>验证URL->>结束");
        } catch (IOException e){
            e.printStackTrace();
        } catch (XmlPullParserException e){
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e){
            e.printStackTrace();
        } catch (RegionConflictException e){
            e.printStackTrace();
        } catch (InvalidKeyException e){
            e.printStackTrace();
        } catch (InvalidPortException e){
            e.printStackTrace();
        } catch (io.minio.errors.InternalException e){
            e.printStackTrace();
        } catch (NoResponseException e){
            e.printStackTrace();
        } catch (InvalidBucketNameException e){
            e.printStackTrace();
        } catch (InsufficientDataException e){
            e.printStackTrace();
        } catch (InvalidEndpointException e){
            e.printStackTrace();
        } catch (ErrorResponseException e){
            e.printStackTrace();
        } catch (InvalidExpiresRangeException e){
            e.printStackTrace();
        }
            return falg;
    }

    @Override
    public String getUrlById(String id) {
        //先验证并更新URL
        validationUrlOrUpdate(id);
        //从redis中获取URL并返回
        String uploadFileStr = stringDao.getValue(id);
        String url = "";
        if(StringUtils.isNotEmpty(uploadFileStr)) {
            UploadFile uploadFile = FastJsonUtils.json2obj(uploadFileStr, UploadFile.class);
            url = uploadFile.getUrl();
        }
        return url;
    }


    /**
     * 获取MinioClient
     * @return
     * @throws InvalidPortException
     * @throws InvalidEndpointException
     * @throws IOException
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws InsufficientDataException
     * @throws InternalException
     * @throws NoResponseException
     * @throws InvalidBucketNameException
     * @throws XmlPullParserException
     * @throws ErrorResponseException
     * @throws RegionConflictException
     */
    private MinioClient getMinioClient() throws InvalidPortException, InvalidEndpointException, IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, InternalException, NoResponseException, InvalidBucketNameException, XmlPullParserException, ErrorResponseException, RegionConflictException, io.minio.errors.InternalException {
        MinioClient minioClient = new MinioClient(Const.MINIO_ENDPOINT, Const.MINIO_PORT, Const.MINIO_ACCESSKEY, Const.MINIO_SECRETKEY, false);
        if(!minioClient.bucketExists(Const.MINIO_BUCKETNAME)){
            minioClient.makeBucket(Const.MINIO_BUCKETNAME);
        }
        return minioClient;
    }


    /**
     * 存放文件资源(inputStream)
     * @param bucket         桶的名称
     * @param objectName     桶中的对象名称
     * @param inputStream    文件流
     * @param contentType    文件类型
     * @throws IOException
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws InsufficientDataException
     * @throws InvalidArgumentException
     * @throws NoResponseException
     * @throws InvalidBucketNameException
     * @throws XmlPullParserException
     * @throws InternalException
     */
    public void putObjectWithRetry(String bucket, String objectName, InputStream inputStream, String contentType, MinioClient minioClient) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, InvalidArgumentException, NoResponseException, InvalidBucketNameException, XmlPullParserException, InternalException, io.minio.errors.InternalException {
        int current = 0;
        boolean isSuccess = false;
        while (!isSuccess && current < RETRY_NUM) {
            try {
                minioClient.putObject(bucket, objectName, inputStream, contentType);
                isSuccess = true;
                logger.info("putObject inputStream, bucket = " + bucket + ", objectName = " + objectName + ", success !");
            } catch (ErrorResponseException e) {
                current++;
                logger.error("putObject inputStream, ErrorResponseException occur for time = " + current, e);
            }
        }
        if (current == RETRY_NUM) {
            logger.warn("putObject inputStream, bucket = " + bucket + ", objectName = " + objectName + ", failed finally!");
        }
    }

    /**
     * 获取随机名字
     * @param uuid
     * @return
     */
    private String getRandomName(String uuid) {
        StringBuffer fileName = new StringBuffer();
        fileName.append(uuid.toUpperCase().substring(0, 5));
        fileName.append(ThreadLocalRandom.current().nextInt(10000, 99999));
        return fileName.toString();
    }

    /**
     * 获取uuid
     */
    private String getUUID() {
        return UUID.randomUUID().toString().replace("-", "");
    }

}
